#pragma once

#include <iostream>
#include <memory>
#include <string>
#include <functional>
#include <unordered_map>
#include <cerrno>
#include "Log.hpp"
#include "Epoller.hpp"
#include "nocopy.hpp"
#include "Sockets.hpp"
#include "Common.hpp"

using namespace std;

class Connection;
class TcpServer;

uint32_t EVENT_IN = (EPOLLIN | EPOLLET);
uint32_t EVENT_OUT = (EPOLLOUT | EPOLLET);
const static int buffer_size = 128;

using func_t = function<void(weak_ptr<Connection>)>;

class Connection
{
public:
    Connection(int sock)
        : _sock(sock)
    {}

    void AppendInBuffer(const std::string &info)
    {
        _inbuffer += info;
    }
    void AppendOutBuffer(const std::string &info)
    {
        _outbuffer += info;
    }

    string &InBuffer()
    {
        return _inbuffer;
    }
    string &OutBuffer()
    {
        return _outbuffer;
    }

    void SetHandler(func_t recv_cb, func_t send_cb, func_t except_cb)
    {
        _recv_cb = recv_cb;
        _send_cb = send_cb;
        _except_cb = except_cb;
    }

    void SetWeakPtr(weak_ptr<TcpServer> tcp_server_ptr)
    {
        _tcp_server_ptr = tcp_server_ptr;
    }

    int GetFd()
    {
        return _sock;
    }

    ~Connection()
    {}

private:
    int _sock;
    string _inbuffer;
    string _outbuffer;

public:
    func_t _recv_cb;
    func_t _send_cb;
    func_t _except_cb;

    weak_ptr<TcpServer> _tcp_server_ptr;

    string _ip;
    uint16_t _port;
};

class TcpServer : public nocopy, public enable_shared_from_this<TcpServer>
{
    static const int num = 64;
public:
    TcpServer(uint16_t port, func_t OnMessage)
        : _epoll_ptr(new Epoller())
        , _listensock_ptr(new Sockets)
        , _OnMessage(OnMessage)
        , _port(port)
        , _quit(false)
    {}

    void Init()
    {
        _listensock_ptr->Socket();
        SetNonBlock(_listensock_ptr->Fd());
        _listensock_ptr->Bind(_port);
        _listensock_ptr->Listen();
        log(Info, "create listen socket success: %d", _listensock_ptr->Fd());
        AddConnetion(_listensock_ptr->Fd(), EVENT_IN,
            bind(&TcpServer::Accepter, this, ::placeholders::_1), nullptr, nullptr);
    }

    void Start()
    {
        _quit = true;
        while(_quit)
        {
            Dispatcher(3000);
            PrintConnect();
        }
        _quit = false;
    }

    void Dispatcher(int timeout)
    {
        int n = _epoll_ptr->EpollWait(revs, num, timeout);
        for(int i = 0; i < n; i++)
        {
            uint32_t event = revs[i].events;
            int sock = revs[i].data.fd;
            // 统一把异常事件转化为读写问题
            // if(event & EPOLLERR)
            //     event |= (EPOLLIN | EPOLLOUT);
            // if(event & EPOLLHUP)
            //     event |= (EPOLLIN | EPOLLOUT);
            if((event & EPOLLIN) && IsConnectionSafe(sock))
                if(_connections[sock]->_recv_cb)
                    _connections[sock]->_recv_cb(_connections[sock]);
            if((event & EPOLLOUT) && IsConnectionSafe(sock))
                if(_connections[sock]->_send_cb)
                    _connections[sock]->_send_cb(_connections[sock]);
        }
    }

    // 链接管理器
    void Accepter(weak_ptr<Connection> conn)
    {
        auto connection = conn.lock();
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = ::accept(connection->GetFd(), (struct sockaddr*)&peer, &len);
            if(sock > 0)
            {
                uint16_t peerport = ntohs(peer.sin_port);
                char ipbuf[128];
                inet_ntop(AF_INET, &peer.sin_addr.s_addr, ipbuf, sizeof(ipbuf));
                log(Debug, "get a new client, get info-> [%s:%d], sockfd : %d", ipbuf, peerport, sock);

                SetNonBlock(sock);
                AddConnetion(sock, EVENT_IN, 
                            bind(&TcpServer::Recver, this, ::placeholders::_1), 
                            bind(&TcpServer::Sender, this, ::placeholders::_1), 
                            bind(&TcpServer::Excepter, this, ::placeholders::_1),
                            ipbuf, peerport);
            }
            else
            {
                if(errno == EWOULDBLOCK)
                    break;
                else if(errno == EINTR)
                    continue;
                else
                    break;
            }
        }
    }

    void Recver(weak_ptr<Connection> conn)
    {
        if(conn.expired())
            return;
        auto connection = conn.lock();
        int sock = connection->GetFd();
        while(true)
        {
            char buffer[buffer_size];
            memset(buffer, 0, sizeof(buffer));
            ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
            if(n > 0)
            {
                connection->AppendInBuffer(buffer);
            }
            else if(n == 0)
            {
                log(Info, "sockfd: %d, client info %s:%d quit...", sock, connection->_ip.c_str(), connection->_port);
                connection->_except_cb(connection);
            }
            else
            {
                if(errno == EWOULDBLOCK)
                    break;
                else if(errno == EINTR)
                    continue;
                else
                {
                    log(Warning, "sockfd: %d, client info %s:%d recv error...", sock, connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }
        _OnMessage(connection);
    }

    void Sender(weak_ptr<Connection> conn)
    {
        if(conn.expired())
            return;
        auto connection = conn.lock();
        string& outbuffer = connection->OutBuffer();
        int sock = connection->GetFd();
        while (true)
        {
            ssize_t n = send(sock, outbuffer.c_str(), sizeof(outbuffer), 0);
            if(n > 0)
            {
                outbuffer.erase(0, n);
                if(outbuffer.empty())
                    break;
            }
            else if(n == 0)
            {
                break;
            }
            else
            {
                if(errno == EWOULDBLOCK)
                    break;
                else if(errno == EINTR)
                    continue;
                else
                {
                    log(Warning, "sockfd: %d, client info %s:%d send error...", sock, connection->_ip.c_str(), connection->_port);
                    connection->_except_cb(connection);
                    return;
                }
            }
        }
        if(!outbuffer.empty()) // 读事件未完成，设置对读事件的关心
        {
            EnableEvent(sock, true, true);
        }
        else
        {
            EnableEvent(sock, true, false);
        }
    }

    void EnableEvent(int sock, bool readable, bool writeable)
    {
        uint32_t event = 0;
        event |= ((readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0) | EPOLLET);
        _epoll_ptr->EpollCtl(EPOLL_CTL_MOD, sock, event);
    }

    void Excepter(weak_ptr<Connection> conn)
    {
        if(conn.expired())
            return;
        auto connection = conn.lock();
        int fd = connection->GetFd();
        log(Warning, "Excepter hander sockfd: %d, client info %s:%d excepter handler",
           connection->GetFd(), connection->_ip.c_str(), connection->_port);
        // 1. 移除对特定fd的关心
        _epoll_ptr->EpollCtl(EPOLL_CTL_DEL, fd, 0);
        // 2. 关闭异常的文件描述符
        log(Debug, "Close %d done!", fd);
        close(fd);
        // 3. 从unordered_map中移除
        log(Debug, "Remove %d from _connections...\n", fd);
        _connections.erase(fd);
    }

    bool IsConnectionSafe(int fd)
    {
        auto iter = _connections.find(fd);
        if (iter == _connections.end())
            return false;
        else
            return true;
    }

    void AddConnetion(int sock, uint32_t event, func_t recv_cb,func_t send_cb, func_t except_cb, 
                    const string& ip="0.0.0.0", uint16_t port = 0)
    {
        // 1. 给sock也建立一个connection对象，将listensock添加到Connection中
        // 同时，listensock和Connecion放入_connections
        shared_ptr<Connection> new_connection(new Connection(sock));
        new_connection->SetWeakPtr(shared_from_this()); // shared_from_this(): 返回当前对象的shared_ptr
        new_connection->SetHandler(recv_cb, send_cb, except_cb);
        new_connection->_ip = ip;
        new_connection->_port = port;
        // 2. 添加到unordered_map
        _connections.insert(make_pair(sock, new_connection));
        // 3. 我们添加对应的事件，除了要加到内核中，fd， event
        _epoll_ptr->EpollCtl(EPOLL_CTL_ADD, sock, event);
    }


    void PrintConnect()
    {
        cout << "connect fd list: ";
        for(auto& connect : _connections)
        {
            cout << connect.second->GetFd() << " ";
            cout << connect.second->InBuffer() << endl;
        }
        cout << endl;
    }

    ~TcpServer()
    {}

private:
    shared_ptr<Epoller> _epoll_ptr;
    shared_ptr<Sockets> _listensock_ptr;
    unordered_map<int, shared_ptr<Connection>> _connections;
    uint16_t _port;
    struct epoll_event revs[num];
    bool _quit;
    func_t _OnMessage;
};
